使用 QT6 实现截图功能 实现简介 改了一下 Qt项目中,实现屏幕截图功能的模块详细实现  的代码
截图区域的获取。监听鼠标点击,移动,释放事件,获取需要截图区域的左上角和右下角的坐标。 
截图区域的绘制。使用 drawPixmap 绘制截图区域。 
截图的保存。通过 qt 提供的方法保存文件即可。 
其他,鼠标右键的选项菜单通过 menu 实现,使用槽机制添加各个选项的对应方法。 
 
学习总结 在截图区域点击鼠标右键,会唤起菜单。主要实现是通过 contextMenuEvent 事件和 QMenu 实现。
主要代码
1 2 3 4 5 6 7 menu = new  QMenu (this ); menu->addAction ("保存当前截图" , this , SLOT (saveScreen ())); menu->addAction ("保存全屏截图" , this , SLOT (saveFullScreen ())); menu->addAction ("截图另存为" , this , SLOT (saveScreenOther ())); menu->addAction ("全屏另存为" , this , SLOT (saveFullOther ())); menu->addAction ("退出截图" , this , SLOT (hide ())); 
 
1 2 3 4 5 6 void  ScreenWidget::contextMenuEvent (QContextMenuEvent *)  {    this ->setCursor (Qt::ArrowCursor);     menu->exec (cursor ().pos ()); } 
 
鼠标点击,释放,移动事件 通过重写 Widget 的方法来实现鼠标相关事件的监听
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 void  ScreenWidget::mousePressEvent (QMouseEvent *e)  {    int  status = screen->getStatus ();          if  (status == Screen::CLICK) {         screen->setStart (e->pos ());     } else  if  (status == Screen::MOV) {                  if  (screen->isInArea (e->pos ()) == false ) {                          screen->setStart (e->pos ());             screen->setStatus (Screen::CLICK);         } else  {                          movPos = e->pos ();             this ->setCursor (Qt::SizeAllCursor);         }     }     this ->update (); } void  ScreenWidget::mouseMoveEvent (QMouseEvent *e)  {    int  status = screen->getStatus ();     if  (status == Screen::CLICK) {                  screen->setEnd (e->pos ());     } else  if  (screen->getStatus () == Screen::MOV) {         QPoint p (e->x () - movPos.x (), e->y () - movPos.y ());         screen->move (p);         movPos = e->pos ();     }     this ->update (); } void  ScreenWidget::mouseReleaseEvent (QMouseEvent *e)  {    if  (screen->getStatus () == Screen::CLICK) {                  screen->setStatus (Screen::MOV);     } else  if  (screen->getStatus () == Screen::MOV) {                  this ->setCursor (Qt::ArrowCursor);     }     this ->update (); } 
 
通常会读取 e 对象中的 button 来判断是鼠标左边点击还是哪里点击。
这里使用了自己定义的状态,是因为截图时会点击鼠标并移动(同时触发点击与移动事件,就不能通过简单的判断事件类型去定义起始坐标),使用自己定义的状态可以更好的管理截图区域的坐标。
paintEvent, showEvent,drawPixmap 截图区域的绘制主要就是通过 QPainter 实现的。
showEvent 会在窗口显示时自动调用,在这个程序里就是实例被创建且调用后。(这也是为什么程序运行后全屏会变为灰色,是 showEvent 绘制出来的背景。
paintEvent 的首次调用是所有 ui 绘制完成后,然后就是 ui 发生变化时触发。在这个程序中,当鼠标点击,鼠标移动以及鼠标释放后,都会执行一句 this->update(),更新 ui,随后触发 paintEvent,在 paintEvent 这个回调函数中通过 drawPixmap 配合坐标和缩放因子绘制截图区域。
核心代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 void  ScreenWidget::paintEvent (QPaintEvent *)  {    int  x = screen->getLeftUp ().x ();     int  y = screen->getLeftUp ().y ();     int  w = screen->getRightDown ().x () - x;     int  h = screen->getRightDown ().y () - y;     QPainter painter (this )  ;          QPen pen;     pen.setColor (Qt::green);     pen.setWidth (2 );      pen.setStyle (Qt::DotLine);     painter.setPen (pen);          painter.drawPixmap (0 , 0 , *bgScreen);     if  (w > 0  && h > 0 ) {                  QRect sourceRect (x * pixelRatio, y * pixelRatio, w * pixelRatio, h * pixelRatio)  ;         QRect targetRect (x, y, w, h)  ;                  painter.drawPixmap (targetRect, *fullScreen, sourceRect);     }          painter.drawRect (x, y, w, h);          pen.setColor (Qt::black);     painter.setPen (pen);     painter.drawText (x + w + 2 , y + 12 ,         tr ("%5 x %6" )         .arg (w * pixelRatio)         .arg (h * pixelRatio)); } void  ScreenWidget::showEvent (QShowEvent *)  {    QPoint point (-1 , -1 )  ;     screen->setStart (point);     screen->setEnd (point);     QScreen *pscreen = QApplication::primaryScreen ();          *fullScreen = pscreen->grabWindow (0 , 0 , 0 , screen->width (), screen->height ());     printf ("width: %d, height: %d" , fullScreen->width (), fullScreen->height ());          QPixmap pix (screen->width(), screen->height())  ;     pix.fill ((QColor (160 , 160 , 160 , 200 )));     bgScreen = new  QPixmap (*fullScreen);     QPainter p (bgScreen)  ;     p.drawPixmap (0 , 0 , pix); } 
 
遇到的问题 源代码中使用 QDesktopWidget 获取屏幕尺寸,QT6 开始不支持这个类了。可以使用 QGuiApplication 代替。通过 QGuiApplication::primaryScreen() 获取。
源代码在本机测试,截图时截图区域会放大,并且边框与图片区域有 padding 原因:在高 DPI 显示器上,系统会对屏幕内容进行缩放(例如 150% 或 200%)。在抓取屏幕时,如果没有考虑高 DPI 缩放因子,就会导致捕获图像在显示时出现放大的情况。
解决方案:使用 QApplication::primaryScreen()->devicePixelRatio() 获取缩放因子,在截图区域绘制时,绘制的坐标都与缩放因子相乘,就可以得到正确的绘制区域。伪代码如下:
1 2 3 4 5 6 7 8 9 qreal pixelRatio = QApplication::primaryScreen ()->devicePixelRatio (); if  (w > 0  && h > 0 ) {         QRect sourceRect (x * pixelRatio, y * pixelRatio, w * pixelRatio, h * pixelRatio)  ;     QRect targetRect (x, y, w, h)  ;          painter.drawPixmap (targetRect, *fullScreen, sourceRect); } 
 
不仅在绘制截图区域的时候需要考虑缩放因子,在截图区域保存时也需要考虑这个问题。
下一步计划 
添加开始界面 
可以反复截图 
出现截图菜单,可以复制到剪切板 
实现常见截图软件应该有的功能